Skip to content

feat(cdp-interact): setFieldValue action for React Hook Form fallback (closes #126 Gap A)#179

Merged
Lykhoyda merged 1 commit into
mainfrom
feat/gh-126-set-field-value
May 26, 2026
Merged

feat(cdp-interact): setFieldValue action for React Hook Form fallback (closes #126 Gap A)#179
Lykhoyda merged 1 commit into
mainfrom
feat/gh-126-set-field-value

Conversation

@Lykhoyda
Copy link
Copy Markdown
Owner

Summary

Closes Gap A of #126 — adds a new cdp_interact action=\"setFieldValue\" for the case where typeText's descendant walk can't find a typeable handler because the field's state flows through React Hook Form's field.onChange → FormProvider context → setValue, with no inner TextInput-shaped fiber for typeText to find.

Gap B (action-sheet portal modal invisibility) is a different fiber-root iteration problem — deferred to a follow-up.

API

cdp_interact({
  action: 'setFieldValue',
  testID: 'email-field',          // anchor inside the form subtree
  name: 'email',                  // RHF field name (matches useController/Controller name)
  value: 'a@b.com',               // any string | number | boolean, passed verbatim
  shouldValidate: true,           // default true; pass-through to setValue options
  shouldDirty: true,              // default true; pass-through to setValue options
})

Algorithm (injected-helpers.ts)

  1. Match the testID anchor (existing infrastructure).
  2. Walk UP via node.return, bounded at 32 levels / 100 fiber visits.
  3. Find the first Provider fiber whose memoizedProps.value duck-types as UseFormReturn: typeof value.setValue === 'function' && typeof value.getValues === 'function' && value.control && typeof value.control === 'object'.
  4. Call value.setValue(name, value, { shouldValidate, shouldDirty }).
  5. Surface the thrown message if setValue rejects (bad field name, value type mismatch, transitioning state).

Closest ancestor wins → nested forms behave like natural React context resolution.

Design decisions

Why explicit action, not implicit typeText fallback. Implicit fallback would change the meaning of an existing tool call and risk surprising users who expected the existing 'no handler' error. Explicit setFieldValue is opt-in, self-contained, and documented in the tool description.

Why walk-up via fiber.return instead of useFormContext(). Injected helpers run OUTSIDE React's rendering cycle, so hooks can't be called. The codebase already uses this fiber-walk pattern for nav state and store context — adding setFieldValue follows the established convention.

Why a strict duck-type (setValue + getValues + control all required). Many Providers expose value objects with one or two look-alike fields. ThemeProvider with .value.theme.setValue (a custom setter) shouldn't be mistaken for a form. Requiring all three RHF surface markers gives a clean signal.

Test plan

8 new tests in gh-126-set-field-value.test.js, all using a VM sandbox with synthetic fiber trees (consistent with existing injected-helpers tests):

  • Happy path — walks up to FormProvider, calls setValue with defaults
  • Options pass-throughshouldValidate=false and shouldDirty=false reach setValue verbatim
  • No coercion — numeric and boolean values pass through unchanged
  • Nested forms — closest provider wins (proves React context semantics)
  • Validation — missing opts.name returns clear error without walking the tree
  • No form ancestor — returns actionable hint pointing at <FormProvider>
  • Throwing setValue — caught and thrown message surfaced (bad field name etc.)
  • Strict duck-type — rejects Providers whose value lacks setValue

Total: 1514/1514 cdp-bridge unit tests passing (+8 net new).

Other changes:

  • Helpers bundle version bumped 21 → 22 (existing source-guard tests + version-pin assertion both updated)
  • cdp_interact action enum gains 'setFieldValue' AND 'longPress' (latter was in the description string but missing from the TypeScript type union — fixed for consistency)
  • 4 new args: name, value, shouldValidate, shouldDirty (all optional; required only for setFieldValue, enforced at handler boundary)

Note on review

codex-pair did not review this commit — the ask-codex MCP server appears disconnected this session (no log entries since May 20). The 8-test coverage + the explicit design approval before implementation are the safety net. Manual /codex-review welcome from reviewers if a deeper second-opinion is wanted.

Refs

🤖 Generated with Claude Code

…closes #126 Gap A)

Adds cdp_interact action="setFieldValue" — explicit React Hook Form
escape hatch for when typeText's onChangeText/onChange descendant walk
can't find a typeable handler. Closes Gap A of #126.

Background: typeText handles the wrapper-Pressable case (depth-bounded
descendant walk for inner TextInput) but fails when a field's value
flows through field.onChange → FormProvider context → setValue with
no inner TextInput-shaped fiber to find. Common with design-system
TextFields built as custom Controllers around <Pressable> + custom
rendering, where the agent reports "no handler" but the form IS fillable
via context.setValue.

API:

  cdp_interact({
    action: 'setFieldValue',
    testID: 'email-field',          // anchor inside the form subtree
    name: 'email',                  // RHF field name (matches useController/Controller name)
    value: 'a@b.com',               // any string | number | boolean, passed verbatim
    shouldValidate: true,           // default true; pass-through to setValue options
    shouldDirty: true,              // default true; pass-through to setValue options
  })

Algorithm (injected-helpers.ts setFieldValue handler):
  - Match the testID anchor (existing infrastructure)
  - Walk UP via node.return, bounded at 32 levels / 100 fiber visits
  - Look for the first Provider fiber whose memoizedProps.value
    duck-types as UseFormReturn: typeof value.setValue === 'function'
    AND typeof value.getValues === 'function' AND value.control is an object
  - Call value.setValue(name, value, {shouldValidate, shouldDirty})
  - Surface the thrown message if setValue rejects (bad field name etc.)

Closest ancestor wins (natural React context resolution) — nested forms
behave intuitively.

Why walk-up via fiber.return instead of useFormContext(): injected
helpers run OUTSIDE React's rendering cycle, so hooks can't be called.
The codebase already uses this pattern (memoizedProps walk) for nav
and store context — adding setFieldValue follows the established
convention.

Why explicit action instead of implicit typeText fallback: the design
question was deliberate (per discussion). Implicit fallback would change
the meaning of an existing tool call and risk surprising users who
expected the existing "no handler" error. Explicit setFieldValue is
opt-in, self-contained, and documented in the tool description.

Tests (8 new in gh-126-set-field-value.test.js, all green):
- happy path: walks up to FormProvider, calls setValue with defaults
- shouldValidate/shouldDirty pass through verbatim
- numeric and boolean values pass through without coercion
- nested-forms: closest provider wins (React context semantics)
- missing opts.name → clear error, does NOT walk the tree
- no FormProvider ancestor → actionable hint pointing at <FormProvider>
- setValue throwing → caught + thrown message surfaced
- duck-type rejects Providers missing setValue (e.g. ThemeProvider)

Other changes:
- Helpers bundle version bumped 21 → 22 (existing source-guard test +
  version-pin assertion both updated)
- cdp_interact schema: action enum gains 'setFieldValue' + 'longPress'
  (latter was already in the description string but missing from the TS
  type union — fixed for consistency)
- New args: name, value, shouldValidate, shouldDirty (all optional;
  required only for setFieldValue, enforced at handler boundary)

Note: codex-pair did not review this commit (the codex MCP server
appears disconnected this session — no log entries since May 20). The
8-test coverage + the explicit design approval are the safety net.

Verified: 1514/1514 cdp-bridge unit tests passing (+8 net new).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Lykhoyda Lykhoyda merged commit ecb6512 into main May 26, 2026
7 checks passed
@Lykhoyda Lykhoyda deleted the feat/gh-126-set-field-value branch May 26, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant